Add audit command to scan packages for security advisories#19
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new stasis audit CLI command intended to collect package versions from stasis artifacts and query npm security advisories.
Changes:
- Added
src/audit.jsfor package extraction, advisory flattening, table formatting, and reporting. - Added
stasis auditcommand wiring in the CLI. - Added audit-focused tests for package collection, formatting, and basic CLI errors.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
bin/stasis.js |
Adds usage text and command dispatch for audit. |
src/audit.js |
Implements package collection, npm advisory lookup, and audit report formatting. |
tests/audit.test.js |
Adds tests for audit helpers and basic CLI error handling. |
Comments suppressed due to low confidence (2)
src/audit.js:30
stasisbundles generated byState.sourceDatacontain loaded source files, formats, and imports, but they do not persist package metadata or package.json files unless those package.json files were themselves imported. As a result, auditing a normal generated bundle will collect zero packages and falsely print "No advisories found", which defeats the bundle-audit path.
for (const [path, source] of Object.entries(json.sources)) {
if (!isPackageJsonPath(path)) continue
const { name, version } = JSON.parse(source)
if (name && version) out.push({ name, version })
src/audit.js:36
- The lockfile shape check is loose enough to accept malformed objects such as
{ version: 0, sources: {} }that do not contain the requiredmodulessection, leading to a successful audit with zero packages. Since generated lockfiles always includemodules, this should reject incomplete lockfiles instead of treating them as clean.
function isLockfileShape(json) {
return Boolean(json && (json.modules || (json.sources && Object.values(json.sources).every((v) => v && typeof v === 'object' && 'name' in v))))
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2c5125c to
fd4d161
Compare
Collects package name/version pairs from stasis lockfiles and brotli bundles, deduplicates them, queries the npm bulk advisories endpoint, and prints results as a table sorted by severity.
- Skip workspace/first-party modules so private package names are not sent to the public npm advisories endpoint. - Show installed version(s) affected by each advisory by joining via semver.satisfies against vulnerable_versions. - Add a 30s timeout (AbortSignal.timeout) to the registry request and wrap non-2xx / network failures in a clear Error with status + body. - Cover the audit() path with tests that stub globalThis.fetch (happy path body + report, and 503 error wrapping).
- parseFile: sniff the first byte to route lockfile vs bundle so a parse failure surfaces the correct diagnostic instead of swallowing the real error and reporting the wrong format. - flattenAdvisories: drop rows where no installed version matches the advisory's vulnerable range (avoids false positives, including bogus non-zero exit codes from such rows). - collectPackages: sort via localeCompare + semver.compare (matches the style already used in apis/npm/index.js). - formatTable: collapse newlines in cells so titles containing \n don't break the box layout. - Add 'none' to SEVERITY_ORDER (npm emits it occasionally). - Drop redundant truthy check in apis/npm/index.js asserts. - Tests: dedup doesn't collapse different packages at the same version; bundle and lockfile diagnostics route correctly; zero-match advisory drop; network/abort error wrapping.
- parseFile now sniffs the bundle shape after brotli decompression and routes to Bundle.parseCode or Bundle.parseResources accordingly. Resource bundles (stasis.resources.br) carry the same per-module name/version metadata as code bundles, so they're auditable too. - Wrap readFileSync ENOENT in a clean 'File not found: <path>' error instead of letting Node's raw stack escape to the CLI.
- parseFile: the JSON.parse used to disambiguate code-vs-resource
bundles was outside the try/catch — a brotli-valid but JSON-corrupt
input leaked a raw SyntaxError. Move it inside the catch so all
bundle parse failures surface 'Failed to parse stasis bundle'.
- ENOENT throw now preserves { cause }, matching the sibling read-
error throw.
- cell() regex broadened to include bare CR and U+2028/U+2029 so
Unicode line/paragraph separators in advisory titles can't break
the box layout (written as escape sequences to survive editor
whitespace-trim).
- printAuditReport now hints 'No node_modules entries found in the
input files' when the caller passed files but they yielded zero
audit-eligible packages — silent 0-scan is confusing.
14d180e to
e0c94bd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a new
auditcommand to the stasis CLI that scans packages from lockfiles and bundles for security vulnerabilities using the npm advisories API.Key Changes
New audit module (
src/audit.js): Core functionality for auditing packagescollectPackagesFromFile(): Extracts package name/version from stasis lockfiles or brotli-compressed bundlescollectPackages(): Aggregates packages from multiple files with deduplicationflattenAdvisories(): Transforms advisory results into sortable rows, ordered by severity then package nameformatTable(): Generates aligned tabular output for advisory reportsaudit(): Main function that collects packages and queries npm advisories APIprintAuditReport(): Formats and outputs audit results to stdout/stderrCLI integration (
bin/stasis.js): Addedstasis auditcommandComprehensive test suite (
tests/audit.test.js): 173 lines of tests coveringImplementation Details
https://claude.ai/code/session_012AEjpn3Kc2i8WfHw5ZgPEv